import java.util.ArrayList;
import java.util.List;


public class Node implements Cloneable {

	public final static int MAX_PLAYER = 0;
	public final static int MIN_PLAYER = 1;
	
	private final static int DRAW = 0;

	private Board board; // holds the node's state (the board's state)
	private int player;
	private MovePerformer usedMove; // holds the used move that led to this node
	private List<Node> children = new ArrayList<Node>(); // holds the list of children for this node
	private Node pathParent; // holds a reference to this node's parent in the graph
	private int level; // holds the level of depth of this node in the graph

	public List<Node> getChildren() {
		return children;
	}

	public void setChildren(List<Node> children) {
		this.children = children;
	}

	public void addChild(Node node) {
		if(this.children == null) this.children = new ArrayList<Node>();
		this.children.add(node);
	}

	public Node getPathParent() {
		return pathParent;
	}

	public void setPathParent(Node pathParent) {
		this.pathParent = pathParent;
	}

	public Board getBoard() {
		return board;
	}

	public void setBoard(Board board) {
		this.board = board;
	}

	public MovePerformer getUsedMove() {
		return usedMove;
	}

	public void setUsedMove(MovePerformer usedMove) {
		this.usedMove = usedMove;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public int getLevel() {
		return level;
	}

	public int getPlayer() {
		return player;
	}

	public void setPlayer(int player) {
		this.player = player;
	}

	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException ("This class does not implement Cloneable.");
		}
	} 

	public Node childClone() {
		Node child = (Node) clone();
		child.setPathParent(this);
		child.setLevel(getLevel()+1);
		child.setPlayer(getPlayer() == MAX_PLAYER ? MIN_PLAYER : MAX_PLAYER);
		return child;
	}

	public boolean isGoal() {
		boolean isOpponentKingInBoard = false;
		for (Piece p : board.getOpponentPieces()) {
			if(p.getKind() == Piece.TYPE_KING){
				isOpponentKingInBoard = true;
			}
		}
		return !isOpponentKingInBoard;
	}

	public ArrayList<Node> expand() {
		ArrayList<Node> children = new ArrayList<Node>();
		BoardMoveGenerator bmv = new BoardMoveGenerator(getBoard());
		for (Piece p : getBoard().getPlayerPieces()) {
			ArrayList<BoardMove> possibleMoves = bmv.getPossibleMoves(p).getAllMoves();
			for (BoardMove boardMove : possibleMoves) {
				Node child = childClone();
				child.setUsedMove(new MovePerformer(p, boardMove));
				children.add(child);
			}
		}
		setChildren(children);
		return children;
	}

	public double utility() {
		double myPieces = getTypicalPointValuesForPiece(getBoard().getPlayerPieces());
		double opponentPieces = getTypicalPointValuesForPiece(getBoard().getOpponentPieces());

		int proximityToOppKing = 0;
		if(getUsedMove() != null){
			BoardCell oppKing = board.getKingCell(board.getOpponentColor());
			BoardCell targetCellToMove = new BoardCell(getUsedMove().getMove().getToFile(),
					getUsedMove().getMove().getToRank());
			proximityToOppKing = targetCellToMove.getFileDifference(oppKing);
			proximityToOppKing += targetCellToMove.getRankDifference(oppKing);
		}

		// the best node will be the one where the player has the biggest number
		// of pieces while the opponent has the smallest number of pieces
		// and the move chosen makes the piece as closer to the opponnent's
		// king as possible
		return myPieces - opponentPieces - proximityToOppKing;
	}

	private double getTypicalPointValuesForPiece(ArrayList<Piece> pieces){
		double typicalPointValuesForPieces = 0;
		for (Piece p : pieces) {
			switch (p.getKind()) {
			case Piece.TYPE_QUEEN:
				typicalPointValuesForPieces += 9;
				break;
			case Piece.TYPE_BISHOP:
				typicalPointValuesForPieces += 3;
				break;
			case Piece.TYPE_KNIGHT:
				typicalPointValuesForPieces += 3;
				break;
			case Piece.TYPE_ROOK:
				typicalPointValuesForPieces += 5;
				break;
			case Piece.TYPE_PAWN:
				typicalPointValuesForPieces += 1;
				break;
			default:
				break;
			}
		}
		return typicalPointValuesForPieces;
	}
}
